package qy.jalgotrade.utils.plotter;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jep.JepException;
import jep.python.PyObject;
import qy.jalgotrade.bar.Bar;
import qy.jalgotrade.utils.CommonUtils;

/**
 * @author c-geo
 *
 */
public abstract class Series<E, T> {

	private static final Logger logger = LoggerFactory.getLogger(Series.class);

	private final String __label;

	private final Bar.Frequency __frequency;

	private final List<ZonedDateTime> __dateTimes;

	private final Map<ZonedDateTime, E> __values;

	/**
	 * 
	 * @param frequency frequency
	 */
	public Series(Bar.Frequency frequency) {

		this(frequency, null);
	}

	/**
	 * 
	 * @param frequency frequency
	 * @param label label
	 */
	public Series(Bar.Frequency frequency, String label) {

		__label = label;
		__frequency = frequency;
		__dateTimes = new ArrayList<>();
		__values = new HashMap<>();
	}

	/**
	 * 
	 * @return __label
	 */
	public String getLabel() {

		return __label;
	}

	/**
	 * 
	 * @return __frequency
	 */
	public Bar.Frequency getFrequency() {

		return __frequency;
	}

	/**
	 * 
	 * @return __dateTimes
	 */
	public List<ZonedDateTime> getDateTimes() {

		return __dateTimes;
	}

	/**
	 * 
	 * @return __values
	 */
	public Map<ZonedDateTime, E> getValues() {

		return __values;
	}

	/**
	 * 
	 * @param dateTime dateTime
	 * @param value value
	 */
	public void addValue(ZonedDateTime dateTime, E value) {

		__values.put(dateTime, value);
		int idx = Collections.binarySearch(__dateTimes, dateTime);
		if (idx < 0) {
			__dateTimes.add(-(idx + 1), dateTime);
		} else {
			/*throw new IllegalArgumentException("Duplicated datetime to add: " + dateTime);*/
			logger.debug("Duplicated datetime to add: {} (value: {}, exists value: {})", dateTime, value,
			        __values.get(dateTime));
		}
	}

	/**
	 * 
	 * @param dateTime dateTime
	 * @return value
	 */
	@SuppressWarnings("unchecked")
	public T getValue(ZonedDateTime dateTime) {

		return (T) __values.get(dateTime);
	}

	/**
	 * 
	 * @return color
	 */
	public String getColor() {

		return "";
	}

	/**
	 * 
	 * @return marker
	 */
	public abstract String getMarker();

	/**
	 * 
	 * @return boolean
	 */
	public abstract boolean needColor();

	/**
	 * 
	 * @param mplSubplot mplSubplot
	 * @param dateTimes dateTimes
	 * @param color color
	 * @throws JepException JepException
	 */
	public void plot(PyObject mplSubplot, List<ZonedDateTime> dateTimes, String color) throws JepException {

		if (dateTimes.size() == 0) {
			return;
		}

		PyObject dateTimeIdx = StrategyPlotter.jepInterpreter().getValue("plotterDateTimeIdx", PyObject.class);
		PyObject values = CommonUtils.createPyList(StrategyPlotter.jepInterpreter(),
		        CommonUtils.genPyObjectName(getClass().getName(), "plot", getLabel() + "_values"),
				true);
		for (ZonedDateTime dateTime : dateTimes) {
			CommonUtils.pyInvoke(values, "append", getValue(dateTime));
		}

		// XXX: qy-pyalgotrade: 将 x 轴由 datetime 映射为 index (跳过非交易时间):
		// mplSubplot.plot([i for i in range(len(dateTimes))], values, color=color, marker=self.getMarker(), label=self.getLabel())
		Object[] args = new Object[] { dateTimeIdx, values };
		// @formatter:off
		Map<String, Object> kwargs = MapUtils.putAll(new HashMap<String, Object>(), new Object[][] {
				{ "color", color },
				{ "marker", getMarker() },
				{ "label", getLabel() }
			});
		// @formatter:on
		CommonUtils.pyInvoke(mplSubplot, "plot", args, kwargs);
		/*
		 * 横轴 (x) 根据 frequency 设置 大小刻度:
		 *     1. 日内级别 (min_30 及以下): 大刻度: week, 小刻度: frequency;
		 *     2. 其他上级别: 不设置 大小刻度:
		 */
		if (__frequency.getValue() <= Bar.Frequency.MINUTE_30.getValue()) {
			// major-locator:
			PyObject datesIdx = CommonUtils.createPyList(StrategyPlotter.jepInterpreter(),
			        CommonUtils.genPyObjectName(getClass().getName(), "plot", getLabel() + "_datesIdx"),
					true);
			LocalDate currDate = null;
			for (int i = 0; i < dateTimes.size(); i++) {
				if (currDate == null || !dateTimes.get(i).toLocalDate().equals(currDate) && (DayOfWeek.MONDAY
				        .equals(dateTimes.get(i).toLocalDate().getDayOfWeek())
				        || dateTimes.get(i).toLocalDate().get(WeekFields.SUNDAY_START.weekOfWeekBasedYear()) != currDate
				                .get(WeekFields.SUNDAY_START.weekOfWeekBasedYear()))) {
					CommonUtils.pyInvoke(datesIdx, "append", i);
					currDate = dateTimes.get(i).toLocalDate();
				}
			}
			// grid 按照 date 划分:
			// mplSubplot.xaxis.set_major_locator(ticker.FixedLocator(datesIdx))
			PyObject arg = CommonUtils.pyInvokeAs(PyObject.class,
			        StrategyPlotter.jepInterpreter().getValue("ticker", PyObject.class), "FixedLocator", datesIdx); // ticker.FixedLocator(datesIdx)
			CommonUtils.pyInvoke(mplSubplot.getAttr("xaxis", PyObject.class), "set_major_locator", arg);
		}
		// mplSubplot.xaxis.set_major_formatter(ticker.FuncFormatter(formatDateMin))
		PyObject arg = CommonUtils.pyInvokeAs(PyObject.class,
		        StrategyPlotter.jepInterpreter().getValue("ticker", PyObject.class), "FuncFormatter",
		        StrategyPlotter.jepInterpreter().getValue("formatDateMin", PyObject.class)); // ticker.FuncFormatter(formatDateMin)
		CommonUtils.pyInvoke(mplSubplot.getAttr("xaxis", PyObject.class), "set_major_formatter", arg);

	}
}
